前言:rmi(remote method invocation)是java官方的远程调用的是一种实现方式,它使得我们能像调用本地服务一般调用远程服务
源码分析 1. 客户端调用 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.rmi.Naming;public class ClientApplication { public static void main (String args[]) { String url = "rmi://localhost:8888/" ; try { ServerService service = (ServerService) Naming.lookup(url + "server-service" ); Class stubClass = service.getClass(); System.out.println(service + " 是 " + stubClass.getName() + " 的实例!" ); Class[] interfaces = stubClass.getInterfaces(); for (Class c : interfaces) { System.out.println("存根类实现了 " + c.getName() + " 接口!" ); } System.out.println(service.service()); } catch (Exception e) { e.printStackTrace(); } } }
分析 1. 在RMI服务注册表中查找服务接口, Naming.lookup(registry url+service name)将会返回 Proxy[ServerService,RemoteObjectInvocationHandler[UnicastRef [liveRef: [endpoint:192.168.1.103:49471 ,objID:[-79051ded:16cec85dc68:-7fff, -7771089289704678398]]]]]
即ServerService的动态代理对象
1 ServerService service = (ServerService) Naming.lookup(url + "server-service" );
2. 进入Naming.lookup方程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public static Remote lookup (String name) throws NotBoundException, java.net.MalformedURLException, RemoteException { ParsedNamingURL parsed = parseURL(name); Registry registry = getRegistry(parsed); if (parsed.name == null ) return registry; return registry.lookup(parsed.name); }
1 Registry registry = getRegistry(parsed);
Registry is a remote interface to a simple remote object registry that provides methods for storing and retrieving remote object references bound with arbitrary string names.
3. Registry接口帮我们拿到我们想要的指定名称的远程服务的reference 即RegistryImpl_Stub
进入registry.lookup(parsed.name)
调用 RegistryImpl_Stub的ref(RemoteRef)对象的newCall()方法,将RegistryImpl_Stub对象传了进去,不要忘了构造它的时候我们将服务器的主机端口等信息传了进去,也就是我们把服务器相关的信息也传进了newCall()方法。newCall()方法做的事情简单来看就是建立了跟远程RegistryImpl的Skeleton对象的连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public Remote lookup (String var1) throws AccessException, NotBoundException, RemoteException { try { RemoteCall var2 = this .ref.newCall(this , operations, 2 , 4905912898345647071L ); try { ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); } catch (IOException var17) { throw new MarshalException("error marshalling arguments" , var17); } this .ref.invoke(var2); Remote var22; try { ObjectInput var4 = var2.getInputStream(); var22 = (Remote)var4.readObject(); } catch (IOException var14) { throw new UnmarshalException("error unmarshalling return" , var14); } catch (ClassNotFoundException var15) { throw new UnmarshalException("error unmarshalling return" , var15); } finally { this .ref.done(var2); } return var22; } catch (RuntimeException var18) { throw var18; } catch (RemoteException var19) { throw var19; } catch (NotBoundException var20) { throw var20; } catch (Exception var21) { throw new UnexpectedException("undeclared checked exception" , var21); } }
4. 客户端获取服务端返回的服务Stub对象,接下来可以利用Stub对象进行远程调用 2. 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import java.rmi.Naming;import java.rmi.registry.LocateRegistry;public class ServerApplication { public static void main (String args[]) { try { ServerService service = new ServerServiceImpl(); LocateRegistry.createRegistry(8888 ); Naming.bind("rmi://localhost:8888/server-service" , service); } catch (Exception e) { e.printStackTrace(); } System.out.println("服务器向命名表注册了1个远程服务对象!" ); } }
1. LocateRegistry.createRegistry(8888)创建RegistryImpl对象 源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public RegistryImpl (final int var1) throws RemoteException { this .bindings = new Hashtable(101 ); if (var1 == 1099 && System.getSecurityManager() != null ) { try { AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { public Void run () throws RemoteException { LiveRef var1x = new LiveRef(RegistryImpl.id, var1); RegistryImpl.this .setup(new UnicastServerRef(var1x, (var0) -> { return RegistryImpl.registryFilter(var0); })); return null ; } }, (AccessControlContext)null , new SocketPermission("localhost:" + var1, "listen,accept" )); } catch (PrivilegedActionException var3) { throw (RemoteException)var3.getException(); } } else { LiveRef var2 = new LiveRef(id, var1); this .setup(new UnicastServerRef(var2, RegistryImpl::registryFilter)); } }
1 2 3 RegistryImpl.this .setup(new UnicastServerRef(var1x, (var0) -> { return RegistryImpl.registryFilter(var0); }));
setUp()方法将指向正在初始化的RegistryImpl对象的远程引用ref(RemoteRef)赋值为传入的UnicastServerRef对象,这里涉及了向上转型。然后继续移交UnicastServerRef的exportObject()方法。
进入UnicastServerRef的exportObject()方法。可以看到,这里首先为传入的RegistryImpl创建一个代理,这个代理我们可以推断出就是后面服务于客户端的RegistryImpl的Stub对象。然后将UnicastServerRef的skel(skeleton)对象设置为当前RegistryImpl对象。最后用skeleton、stub、UnicastServerRef对象、id和一个boolean值构造了一个Target对象,也就是这个Target对象基本上包含了全部的信息。调用UnicastServerRef的ref(LiveRef)变量的exportObject()方法。
到上面为止,我们看到的都是一些变量的赋值和创建工作,还没有到连接层,这些引用对象将会被Stub和Skeleton对象使用。接下来就是连接层上的了。追溯LiveRef的exportObject()方法,很容易找到了TCPTransport的exportObject()方法。这个方法做的事情就是将上面构造的Target对象暴露出去。调用TCPTransport的listen()方法,listen()方法创建了一个ServerSocket,并且启动了一条线程等待客户端的请求。接着调用父类Transport的exportObject()将Target对象存放进ObjectTable中
2. 将服务实现绑定到服务端的RegistryImpl上,使得客户端只需与RegistryImpl_Stub交互 总结
当客户端通过RMI注册表找到一个远程接口的时候,所得到的其实是远程接口的一个动态代理对象。
当客户端调用其中的方法的时候,方法的参数对象会在序列化之后,传输到服务器端
服务器端接收到之后,进行反序列化得到参数对象
并使用这些参数对象,在服务器端调用实际的方法
调用的返回值Java对象经过序列化之后,再发送回客户端
客户端再经过反序列化之后得到Java对象,返回给调用者
这中间的序列化过程对于使用者来说是透明的,由动态代理对象自动完成
Stub和Skeleton的作用
Stub对象做的事情是建立到服务端Skeleton对象的Socket连接。将客户端的方法调用转换为字符串标识传递给Skeleton对象。并且同步阻塞等待服务端返回结果
Skeleton对象做的事情是将服务实现传入构造参数,获取客户端通过socket传过来的方法调用字符串标识,将请求转发到具体的服务上面。获取结果之后返回给客户端。